## 一、背景与目标
我在 Claw Cloud(S3 兼容对象存储)上创建了一个 bucket,并绑定自定义域名:
```text
https://images.isrv.cn/
```
目标是构建一个:
- 本地可管理(预览 / 重命名 / 分类)
- 自动同步到对象存储
- 支持直链访问
- 带图床首页(可浏览、搜索、复制 Markdown)
- 完全静态(无后端服务)
---
## 二、最终架构
```text
📁 /home/ding/Pictures/Images ← 本地主库
↓
🐍 generate-images-json.py ← 本地生成索引 JSON
↓
🔁 rclone sync (systemd timer)
↓
☁️ Claw S3 bucket
↓
🌍 https://images.isrv.cn/
```
核心思想:
> **本地目录是唯一真源,远端只负责发布和访问**
---
## 三、目录结构
```text
/home/ding/Pictures/Images/
├── index.html # 图床首页
├── images.json # 本地生成的索引
├── screenshots/
├── posts/
├── wallpapers/
└── ...
```
---
## 四、rclone 配置
已配置远端(示例):
```bash
rclone ls claw:ujwn4e6y-img
```
说明:
- `claw`:远端名称
- `ujwn4e6y-img`:bucket 名
---
## 五、生成图片索引(核心)
### 1. 脚本路径
```bash
/home/ding/.local/bin/generate-images-json.py
```
### 2. 完整代码
```python
#!/usr/bin/env python3
from __future__ import annotations
import json
import mimetypes
from pathlib import Path
from urllib.parse import quote
ROOT = Path("/home/ding/Pictures/Images")
BASE_URL = "https://images.isrv.cn"
OUTPUT = ROOT / "images.json"
IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".svg", ".avif"}
def is_image(path: Path) -> bool:
if path.name in {"index.html", "images.json"}:
return False
return path.suffix.lower() in IMAGE_EXTS
def main():
items = []
for path in ROOT.rglob("*"):
if not path.is_file():
continue
# 忽略隐藏文件
if any(part.startswith(".") for part in path.relative_to(ROOT).parts):
continue
if not is_image(path):
continue
rel = path.relative_to(ROOT).as_posix()
stat = path.stat()
items.append({
"name": path.name,
"path": rel,
"url": f"{BASE_URL}/{quote(rel, safe='/')}",
"mtime": int(stat.st_mtime),
"size": stat.st_size,
"type": mimetypes.guess_type(path.name)[0] or "application/octet-stream",
})
# 按修改时间倒序(最新优先)
items.sort(key=lambda x: x["mtime"], reverse=True)
with OUTPUT.open("w", encoding="utf-8") as f:
json.dump(items, f, ensure_ascii=False, separators=(",", ":"))
if __name__ == "__main__":
main()
```
### 3. 赋权
```bash
chmod +x /home/ding/.local/bin/generate-images-json.py
```
---
## 六、同步脚本
### 1. 路径
```bash
/home/ding/.local/bin/rclone-images-sync.sh
```
### 2. 完整代码
```bash
#!/usr/bin/env bash
set -euo pipefail
SRC="/home/ding/Pictures/Images"
DST="claw:ujwn4e6y-img"
LOG="/home/ding/.local/state/rclone-images-sync.log"
GEN="/home/ding/.local/bin/generate-images-json.py"
mkdir -p "$(dirname "$LOG")"
# 先生成 JSON 索引
"$GEN"
# 再同步到对象存储
exec /usr/bin/rclone sync "$SRC" "$DST" \
--fast-list \
--transfers 8 \
--checkers 16 \
--delete-during \
--track-renames \
--log-file "$LOG" \
--log-level INFO
```
### 3. 赋权
```bash
chmod +x /home/ding/.local/bin/rclone-images-sync.sh
```
---
## 七、systemd 定时任务
### 1. service
路径:
```bash
~/.config/systemd/user/rclone-images-sync.service
```
内容:
```ini
[Unit]
Description=Sync local image directory to Claw object storage
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/home/ding/.local/bin/rclone-images-sync.sh
```
---
### 2. timer
路径:
```bash
~/.config/systemd/user/rclone-images-sync.timer
```
内容:
```ini
[Unit]
Description=Run rclone image sync periodically
[Timer]
OnBootSec=2min
OnUnitActiveSec=5min
Persistent=true
[Install]
WantedBy=timers.target
```
---
### 3. 启用
```bash
systemctl --user daemon-reload
systemctl --user enable --now rclone-images-sync.timer
```
查看:
```bash
systemctl --user list-timers
```
---
### 4. 关键(保持后台运行)
```bash
loginctl enable-linger ding
```
---
## 八、图床首页(index.html)
路径:
```bash
/home/ding/Pictures/Images/index.html
```
(此处省略代码,已在上一条提供完整版本)
----
功能:
- 图片网格展示
- 按时间排序(最新优先)
- 搜索(文件名 / 路径)
- 分页
- 点击缩略图 → 当前页打开(支持 Alt+← 返回)
- 预览按钮 → 新标签打开
- 复制直链
- 复制 Markdown
---
## 九、访问方式
### 单图访问
```text
https://images.isrv.cn/screen_20260329_195241.png
```
### 图床首页
```text
https://images.isrv.cn/
```
---
## 十、验证流程
### 手动执行同步
```bash
/home/ding/.local/bin/rclone-images-sync.sh
```
### 查看远端
```bash
rclone ls claw:ujwn4e6y-img
```
### 浏览器访问
```text
https://images.isrv.cn/
```
---
## 十一、关键设计决策
### 1. 为什么不用 S3 挂载
- 非 POSIX 文件系统
- 预览体验差
- 性能差
- 行为不稳定
👉 放弃 mount,采用直传
---
### 2. 为什么不用 rsync
- rsync 适用于文件系统
- 不理解对象存储语义
👉 使用 rclone 原生支持
---
### 3. 为什么本地生成 JSON
对象存储:
- 不支持目录列表
- 不支持动态查询
👉 本地生成索引 → 静态加载
---
### 4. 为什么用 systemd timer
相比 cron:
- 支持开机补执行(Persistent)
- 更好日志管理
- 可观测性强
---
### 5. 为什么用 sync 而不是 copy
```bash
rclone sync
```
保证:
- 本地删除 → 远端删除
- 本地修改 → 远端更新
👉 保持完全一致
---
## 十二、注意事项
### ⚠️ 删除风险
`sync` 会删除远端文件:
> 本地误删 = 远端也会删除
建议:
- 初期使用 `--dry-run`
- 或增加备份策略
---
### ⚠️ 文件命名建议
推荐:
```text
screen_20260329_195241.png
autumn-leaves.png
post-cover.jpg
```
避免:
- 空格
- 中文(可用但不推荐)
- 特殊符号
---
### ⚠️ URL 编码
已在 Python 中处理:
```python
quote(rel, safe='/')
```
---
## 十三、最终效果
你现在拥有:
- ✔ 本地文件管理体验(完全自由)
- ✔ 自动同步到对象存储
- ✔ 自定义域名访问
- ✔ 可浏览的图床首页
- ✔ 一键复制 Markdown
- ✔ 完全静态,无后端依赖
---
## 十四、一句话总结
> **用本地目录作为主库 + rclone 定时同步 + 本地生成 JSON 索引 + 静态页面渲染,就是一个稳定、可控、零后端的图床方案。**
---
如果以后需要继续升级,这一套可以自然扩展到:
- 自动压缩 / WebP
- Markdown 自动上传工具(类似 PicGo)
- 私有签名 URL
- CDN 加速策略
但就目前而言,这套已经是**工程上非常干净且稳定的方案**了。